package com.me.EpicVN.Zoom;


import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
import android.util.FloatMath;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.RelativeLayout;

import com.me.EpicVN.R;
import com.me.EpicVN.Utli.Constants;

/**
 * Listener for controlling zoom state through touch events
 */
public class TouchZoomListener implements View.OnTouchListener, GestureDetector.OnGestureListener,
        GestureDetector.OnDoubleTapListener {

    private final String TAG = "LongPressZoomListener";

    private final GestureDetector gestureDetector;

    /** Time of tactile feedback vibration when entering zoom mode */
    private static final long VIBRATE_TIME = 50;

    /** Zoom control to manipulate */
    private ZoomControl mZoomControl;

    /** X-coordinate of previously handled touch event */
    private float mX;

    /** Y-coordinate of previously handled touch event */
    private float mY;

    /** X-coordinate of latest down event */
    private float mDownX;

    /** Y-coordinate of latest down event */
    private float mDownY;

    /** Velocity tracker for touch events */
    private VelocityTracker mVelocityTracker;

    /** Distance touch can wander before we think it's scrolling */
    private final int mScaledTouchSlop;

    /** Vibrator for tactile feedback */
    private final Vibrator mVibrator;

    /** Maximum velocity for fling */
    private final int mScaledMaximumFlingVelocity;

    private OnFlingListener onFlingListener;

    public interface OnFlingListener {
        public void onFling(float velocityX);
    }

    public void setOnFlingListener(OnFlingListener onFlingListener) {
        this.onFlingListener = onFlingListener;
    }

    private final Animation popupShow;
    private final Animation popupHide;

    /**
     * Creates a new instance
     * 
     * @param context Application context
     */
    public TouchZoomListener(Context context) {
        gestureDetector = new GestureDetector(context, this);
        mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mScaledMaximumFlingVelocity = ViewConfiguration.get(context)
                .getScaledMaximumFlingVelocity();
        mVibrator = (Vibrator) context.getSystemService("vibrator");
        popupShow = AnimationUtils.loadAnimation(context, R.anim.push_show);
        popupHide = AnimationUtils.loadAnimation(context, R.anim.push_hide);
    }

    /**
     * Sets the zoom control to manipulate
     * 
     * @param control Zoom control
     */
    public void setZoomControl(ZoomControl control) {
        mZoomControl = control;
    }

    /**
     * Runnable that enters zoom mode
     */
    private final Runnable mLongPressRunnable = new Runnable() {
        @Override
        public void run() {
            mVibrator.vibrate(VIBRATE_TIME);
        }
    };

    private RelativeLayout layoutNavigationBar;

    public void setLayout(RelativeLayout layout) {
        layoutNavigationBar = layout;
    }

    private int action = 0, width = 0, height = 0;
    private float currentX = 0, currentY = 0;
    private float scrollX = 0, scrollY = 0;
    private float dist = 0, dx = 0, dy = 0;

    // Definition of the touch states
    private final int NONE = 0;
    private final int ONE_FINGER_DRAG = 1;
    private final int TWO_FINGERS_DRAG = 2;
    private int MODE = NONE;

    private float oldDist;
    private float distBetweenFingers;
    private float lastZooming;

    // Implements View.OnTouchListener
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        action = event.getAction();
        currentX = event.getX();
        currentY = event.getY();

        width = v.getWidth();
        height = v.getHeight();

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
        gestureDetector.onTouchEvent(event);

        switch (action) {
        case MotionEvent.ACTION_DOWN: {
            MODE = ONE_FINGER_DRAG;
            mZoomControl.stopFling();
            mDownX = currentX;
            mDownY = currentY;
            mX = currentX;
            mY = currentY;
            break;
        }
        case MotionEvent.ACTION_POINTER_2_DOWN: {
            // The distance check is done to avoid false alarms
            distBetweenFingers = spacing(event);
            if (distBetweenFingers > 5f) {
                MODE = TWO_FINGERS_DRAG;
            }
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            mZoomControl.stopFling();
            if (MODE == TWO_FINGERS_DRAG) {
                Log.v(TAG, "TWO_FINGERS_DRAG");
                oldDist = distBetweenFingers;
                distBetweenFingers = spacing(event);
                lastZooming = distBetweenFingers / oldDist;
                mZoomControl.zoom(lastZooming, mDownX / width, mDownY / height);
            } else if (MODE == ONE_FINGER_DRAG) {
                Log.v(TAG, "ONE_FINGER_DRAG");
                dx = (currentX - mX) / width;
                dy = (currentY - mY) / height;
                mZoomControl.pan(-dx, -dy);
            } else {
                scrollX = mDownX - currentX;
                scrollY = mDownY - currentY;
                dist = (float) Math.sqrt(scrollX * scrollX + scrollY * scrollY);
                if (dist >= mScaledTouchSlop) {
                    v.removeCallbacks(mLongPressRunnable);
                    MODE = ONE_FINGER_DRAG;
                }
            }
            mX = currentX;
            mY = currentY;
            break;
        }
        case MotionEvent.ACTION_POINTER_UP: {
            mZoomControl.stopFling();
            MODE = ONE_FINGER_DRAG;
            break;
        }
        case MotionEvent.ACTION_POINTER_2_UP: {
            mZoomControl.stopFling();
            MODE = ONE_FINGER_DRAG;
            break;
        }
        case MotionEvent.ACTION_UP: {
            if (MODE == ONE_FINGER_DRAG) {
                mVelocityTracker.computeCurrentVelocity(1000, mScaledMaximumFlingVelocity);
                mZoomControl.startFling(-mVelocityTracker.getXVelocity() / v.getWidth(),
                        -mVelocityTracker.getYVelocity() / v.getHeight());
            } else {
                mZoomControl.startFling(0, 0);
            }
            mVelocityTracker.recycle();
            mVelocityTracker = null;
            MODE = NONE;
            break;
        }
        default: {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
            MODE = NONE;
            break;
        }
        }
        return true;
    }

    private void doubleTabZoom(float zoom) {
        mZoomControl.zoom((float) Math.pow(20, zoom), mDownX / width, mDownY / height);
    }

    private boolean isDoubleTab = false;
    private boolean isZoomIn = false;
    private final Runnable mDoubleZoom = new Runnable() {
        @Override
        public void run() {
            isZoomIn = false;
            mZoomControl.setLimitPan(false);
            Constants.DOUBLE_TAB_RATE_LEVEL_MAX_ZOOM = mZoomControl.getZoomState().getZoom();
            if (Constants.DOUBLE_TAB_RATE_LEVEL_MAX_ZOOM < Constants.DOUBLE_TAB_RATE_LEVEL_ZOOM_1) {
                isZoomIn = true;
                Constants.DOUBLE_TAB_RATE_LEVEL_MAX_ZOOM = Constants.DOUBLE_TAB_RATE_LEVEL_ZOOM_1;
            } else if (Constants.DOUBLE_TAB_RATE_LEVEL_MAX_ZOOM >= Constants.DOUBLE_TAB_RATE_LEVEL_ZOOM_1
                    && Constants.DOUBLE_TAB_RATE_LEVEL_MAX_ZOOM < Constants.DOUBLE_TAB_RATE_LEVEL_ZOOM_2) {
                isZoomIn = true;
                Constants.DOUBLE_TAB_RATE_LEVEL_MAX_ZOOM = Constants.DOUBLE_TAB_RATE_LEVEL_ZOOM_2;
            } else if (Constants.DOUBLE_TAB_RATE_LEVEL_MAX_ZOOM >= Constants.DOUBLE_TAB_RATE_LEVEL_ZOOM_2) {
                mZoomControl.setLimitPan(true);
                Constants.DOUBLE_TAB_RATE_LEVEL_MAX_ZOOM = 1.0f;
            }

            isDoubleTab = true;
            try {
                while (isDoubleTab) {
                    myHandle.sendMessage(myHandle.obtainMessage());
                    Thread.sleep(Constants.DOUBLE_TAB_SPEED_ZOOM);
                }
            } catch (Throwable t) {
                isDoubleTab = false;
            }
        }

        Handler myHandle = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                if (isZoomIn) {
                    if (mZoomControl.getZoomState().getZoom() >= Constants.DOUBLE_TAB_RATE_LEVEL_MAX_ZOOM) {
                        isDoubleTab = false;
                        return;
                    }
                    doubleTabZoom(Constants.DOUBLE_TAB_RATE_ZOOM);
                } else {
                    if (mZoomControl.getZoomState().getZoom() <= Constants.DOUBLE_TAB_RATE_LEVEL_MAX_ZOOM) {
                        isDoubleTab = false;
                        mZoomControl.setLimitPan(false);
                        mZoomControl.startFling(0, 0);
                        return;
                    }
                    doubleTabZoom(-Constants.DOUBLE_TAB_RATE_ZOOM);
                }
                mZoomControl.stopFling();
            }
        };
    };

    private Thread mThreadDoubleZoom = null;

    @Override
    public boolean onDoubleTap(MotionEvent e) {
        mThreadDoubleZoom = new Thread(null, mDoubleZoom, TAG);
        mThreadDoubleZoom.start();
        return false;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        mZoomControl.stopFling();
        if (!Constants.SHOW_HIDE_NAVIGATION_BAR) {
            layoutNavigationBar.setVisibility(View.VISIBLE);
            layoutNavigationBar.startAnimation(popupShow);
            Constants.SHOW_HIDE_NAVIGATION_BAR = true;
        } else {
            layoutNavigationBar.setVisibility(View.GONE);
            layoutNavigationBar.startAnimation(popupHide);
            Constants.SHOW_HIDE_NAVIGATION_BAR = false;
        }
        return false;
    }

    @Override
    public boolean onDown(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        if (onFlingListener != null) {
            if (Math.abs(velocityX) > Math.abs(velocityY)) {
                mZoomControl.stopFling();
                onFlingListener.onFling(velocityX);
                return false;
            }
        }
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return false;
    }

    @Override
    public void onShowPress(MotionEvent e) {
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    private float spacing(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return FloatMath.sqrt(x * x + y * y);
    }

    public void stopThread() {
        if (mThreadDoubleZoom != null) {
            mThreadDoubleZoom.stop();
            mThreadDoubleZoom = null;
        }
    }
}